Inside Macintosh: Imaging with QuickDraw

Previous | Chapter Top | Chapter Contents | Next

Opening and Drawing Pictures

Using File Manager routines, your application can retrieve pictures saved in 'PICT' files; using the GetPicture function, your application can retrieve pictures saved in the resource forks of other file types; and using the Scrap Manager function GetScrap , your application can retrieve pictures stored in the scrap.

Drawing a Picture Stored in a 'PICT' File

Listing 7-2 illustrates an application-defined routine, called MyDrawFilePicture , that uses File Manager routines to retrieve a picture saved in a 'PICT' file. The MyDrawFilePicture routine takes a file reference number as a parameter.

Listing 2 Opening and drawing a picture from disk

FUNCTION MyDrawFilePicture(pictFileRefNum: Integer; destRect: Rect): OSErr;
CONST
    cPicFileHeaderSize = 512;
VAR
    myPic:          PicHandle;
    dataLength:     LongInt;
    err:            OSErr;
BEGIN       {This listing assumes the current graphics port is set.}
    err := GetEOF(pictFileRefNum,dataLength);                   {get file length}
    IF err = noErr THEN BEGIN
        err := SetFPos(pictFileRefNum,fsFromStart,
                            cPicFileHeaderSize);        {move past the 512-byte 'PICT' }
                                                        { file header}
        dataLength := dataLength - cPicFileHeaderSize;                  {remove 512-byte }
                                                { 'PICT' file header from file length}
        myPic := PicHandle(NewHandle(dataLength)); {allocate picture handle}
        IF (err = noErr) & (myPic <> NIL) THEN BEGIN
            HLock(Handle(myPic));           {lock picture handle before using FSRead}
            err := FSRead(pictFileRefNum,dataLength,Ptr(myPic^));                   {read file}
            HUnlock(Handle(myPic));         {unlock picture handle after using FSRead}
            MyAdjustDestRect(myPic,destRect);               {see Listing 7-7}
            DrawPicture(myPic,destRect);
            IF QDError <> noErr THEN
                ; {likely error is that there is insufficient memory}
            KillPicture(myPic);
        END;
    END;
    MyDrawFilePicture := err;
END;

In code not shown in Listing 7-2 , this application uses the File Manager procedure StandardGetFile to display a dialog box that asks the user for the name of a 'PICT' file; using the file system specification record returned by StandardGetFile , the application calls the File Manager function FSpOpenDF to return a file reference number for the file. The application then passes this file reference number to MyDrawFilePicture .

Because every 'PICT' file contains a 512-byte header for application-specific use, MyDrawFilePicture uses the File Manager function SetFPos to skip past this header information. The MyDrawFilePicture function then uses the File Manager function FSRead to read the file's remaining bytes--those of the Picture record--into memory.

The MyDrawFilePicture function creates a handle for the buffer into which the Picture record is read. Passing this handle to the DrawPicture procedure, MyDrawFilePicture is able to replay onscreen the commands stored in the Picture record.

For large 'PICT' files, it is useful to spool the picture data from disk as necessary instead of reading all of it directly into memory. In low-memory conditions, for example, your application might find it useful to create a temporary file on disk for storing drawing instructions; your application can read this information as necessary. The application-defined routine MyReplaceGetPic shown in Listing 7-3 replaces the getPicProc field of the current graphics port's CQDProcs record with an application-defined low-level routine, called MyFileGetPic . While QuickDraw's standard StdGetPic procedure reads picture data from memory, MyFileGetPic reads the picture data from disk. ( Listing 7-10 shows how to replace QuickDraw's standard StdPutPic procedure with one that writes data to a file so that your application can spool a large picture to disk.)

Listing 3 Replacing QuickDraw's standard low-level picture-reading routine

FUNCTION MyReplaceGetPic: QDProcsPtr;
VAR
    currPort:           GrafPtr;
    customProcs:        QDProcs;
    customCProcs:       CQDProcs;
    savedProcs:         QDProcsPtr;
BEGIN
    GetPort(currPort);
    savedProcs := currPort^.grafProcs;              {save current CQDProcs }
                                                    { or QDProcs record}
    IF MyIsColorPort(currPort) THEN                 {this is a color graphics port}
    BEGIN
        SetStdCProcs(customCProcs);             {create new CQDProcs record containing }
                                                { standard Color QuickDraw low-level }
                                                { routines}
        customCProcs.getPicProc := @MyFileGetPic;               {replace StdGetPic with }
                                                                { address of custom }
                                                                { low-level routine }
                                                                { shown in Listing 7-5}
        currPort^.grafProcs := @customCProcs;               {replace current CQDProcs }
                                                            { record}
    END
    ELSE
    BEGIN                                       {this is a basic graphics port}
        SetStdProcs(customProcs);               {create new QDProcs record containing }
                                                { standard basic QuickDraw low-level }
                                                { routines}
        customProcs.getPicProc := @MyFileGetPic;                {replace StdGetPic with }
                                                                { address of custom }
                                                                { low-level routine }
                                                                { shown in Listing 7-5}
        currPort^.grafProcs := @customProcs;                {replace current QDProcs record}
    END;
    MyReplaceGetPic := savedProcs;
END;

Listing 7-4 shows the application-defined procedure MyIsColorPort , which MyReplaceGetPic calls to determine whether to replace the low-level picture-reading routine for a color graphics port or a basic graphics port.

Listing 4 Determining whether a graphics port is color or basic

FUNCTION MyIsColorPort(aPort: GrafPtr): Boolean;
BEGIN
    MyIsColorPort := (aPort^.portBits.rowBytes < 0)
END;

Listing 7-5 shows the application-defined procedure MyFileGetPic , which uses the File Manager function FSRead to read the file with the file reference number assigned to the application-defined global variable gPictFileRefNum .

Listing 5 A custom low-level procedure for spooling a picture from disk

PROCEDURE MyFileGetPic (dataPtr: Ptr; byteCount: Integer);
VAR
    longCount:      LongInt;
    myErr:          OSErr;
BEGIN
    longCount := byteCount;
    myErr := FSRead(gPictFileRefNum, longCount, dataPtr);
END;

Your application does not keep track of where FSRead stops or resumes reading a file. After reading a portion of a file, FSRead automatically handles where to begin reading next. See Inside Macintosh: Files for more information about using FSRead and other File Manager routines to retrieve data stored in files.

Drawing a Picture Stored in the Scrap

As described in the chapter "Scrap Manager" in Inside Macintosh: More Macintosh Toolbox , your application can use the Scrap Manager to copy and paste data within a document created by your application, among different documents created by your application, and among documents created by your application and documents created by other applications. The two standard scrap formats that all Macintosh applications should support are 'PICT' and 'TEXT' .

Listing 7-6 illustrates the application-defined routine MyPastePict , which retrieves a picture stored on the scrap. For example, a user may have copied to the Clipboard a picture created in another application and then pasted the picture into the application that defines MyPastePict . The MyPastePict procedure uses the Scrap Manager procedure GetScrap to get a handle to the data stored on the scrap; MyPastePict then coerces this handle to one of type PicHandle , which it can pass to the DrawPicture procedure in order to replay the drawing commands stored in the scrap.

Listing 6 Pasting in a picture from the scrap

PROCEDURE MyPastePict(destRect: Rect);
VAR
    myPic:          PicHandle;
    dataLength:     LongInt;
    dontCare:       LongInt;
BEGIN
    myPic := PicHandle(NewHandle(0));               {allocate a handle for the picture}
    dataLength :=
                GetScrap(Handle(myPic),'PICT',dontCare);                {get picture in scrap}
    IF dataLength > 0 THEN {ensure there is PICT data}
    BEGIN
        MyAdjustDestRect(myPic,destRect);               {shown in Listing 7-7}
        DrawPicture(myPic,destRect);
        IF QDError <> noErr THEN
            ; {likely error is that there is insufficient memory}
    END
    ELSE
    ; {handle error for len < or = 0 here}
END;

Defining a Destination Rectangle

In addition to taking a handle to a picture as one parameter, DrawPicture also expects a destination rectangle as another parameter. You should specify this destination rectangle in coordinates local to the current graphics port. The DrawPicture procedure shrinks or stretches the picture as necessary to make it fit into this rectangle.

Listing 7-7 shows an application-defined routine called MyAdjustDestRect that centers the picture inside a destination rectangle, which is passed to DrawPicture when it's time to draw the picture. ( MyAdjustDestRect first ensures that the picture fits inside the destination rectangle by scaling the picture if necessary.)

Listing 7 Adjusting the destination rectangle for a picture

PROCEDURE MyAdjustDestRect(aPict: PicHandle; VAR destRect: Rect);
VAR
    r:                              Rect;
    width, height:                  Integer;
    scale, scaleH, scaleV:          Fixed;
BEGIN
    WITH destRect DO BEGIN          {determine width and height of destination rect}
        width := right - left;
        height := bottom - top;
    END;
    r := aPict^^.picFrame;                  {get the bounding rectangle of the picture}
    OffsetRect(r, - r.left, - r.top);               {ensure upper-left corner is (0,0)}
    scale := Long2Fix(1);
    scaleH := FixRatio(width,r.right);                  {get horizontal and vertical }
    scaleV := FixRatio(height,r.bottom);                { ratios of destination rectangle }
                                                        { to bounding rectangle of picture}
    IF scaleH < scale THEN scale := scaleH;                 {if bounding rect of picture }
    IF scaleV < scale THEN scale := scaleV;                 { is greater than destination }
    IF scale <> Long2Fix(1) THEN                            { rect, get scaling factors}
    BEGIN           {scale picture to fit inside destination rectangle}
        r.right := Fix2Long(FixMul(scale,Long2Fix(r.right)));
        r.bottom := Fix2Long(FixMul(scale,Long2Fix(r.bottom)));
     END;
    {next line centers the picture within the destination rectangle}
    OffsetRect(r,(width - r.right) DIV 2,(height - r.bottom) DIV 2);
    destRect := r;
END;

The application calling MyAdjustDestRect begins defining a destination rectangle by determining a target area within a window--perhaps the entire content area of a window, or perhaps an area selected by the user within a window. The application passes this rectangle to MyAdjustDestRect .

A bounding rectangle is stored in the picFrame field of the Picture record for every picture. The MyAdjustDestRect routine uses the boundaries for the picture to determine whether the picture fits within the destination rectangle. If the picture is larger than the destination rectangle, MyAdjustDestRect scales the picture to make it fit the destination rectangle.

The MyAdjustDestRect routine then centers the picture within the destination rectangle. Finally, MyAdjustDestRect assigns the boundary rectangle of the centered picture to be the new destination rectangle. By returning a destination rectangle whose dimensions are identical to those of the bounding rectangle for the picture, MyAdjustDestRect assures that the picture is not stretched when drawn into its window.

To display a picture at a resolution other than the one at which it was created, your application should compute an appropriate destination rectangle by scaling its width and height by the following factor:

scale factor = destination resolution / source resolution

For example, if a picture was created at 300 dpi and you want to display it at 75 dpi, then your application should compute the destination rectangle width and height as 1/4 of those of the picture's bounding rectangle. Your application can use the GetPictInfo function (described on GetPictInfo ) to gather information about a picture. The PictInfo record (described on PictInfo ) returned by GetPictInfo returns the picture's resolution in its hRes and vRes fields. The sourceRect field contains the bounding rectangle for displaying the image at its optimal resolution.

Drawing a Picture Stored in a 'PICT' Resource

To retrieve a picture stored in a 'PICT' resource, specify its resource ID to the GetPicture function, which returns a handle to the picture. Listing 7-8 illustrates an application-defined routine, called MyDrawResPICT , that retrieves and draws a picture stored as a resource.

Listing 8 Drawing a picture stored in a resource file

PROCEDURE MyDrawResPICT(destRect: Rect; resID: Integer);
VAR
    myPic:      PicHandle;
BEGIN
    myPic := GetPicture(resID);             {get the picture from the resource fork}
    IF myPic <> NIL THEN BEGIN
        MyAdjustDestRect(myPic,destRect);           {see Listing 7-7}
        DrawPicture(myPic,destRect);
        IF QDError <> noErr THEN
            ; {likely error is that there is insufficient memory}
    END
    ELSE
    ; {handle the error here}
END;

When you are finished using a picture stored as a 'PICT' resource, you should use the Resource Manager procedure ReleaseResource instead of the QuickDraw procedure KillResource to release its memory.

If you retrieve a picture stored in a 'PICT' resource and pass its handle to the Window Manager procedure SetWindowPic , the Window Manager procedures DisposeWindow and CloseWindow do not delete it; instead, you must call ReleaseResource before calling DisposeWindow or CloseWindow .


© 1997 Apple Computer, Inc.

Previous | Chapter Top | Chapter Contents | Next